package com.bix.util.blizfiles;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;

import com.bix.util.blizfiles.m2.M2CountOffsetPair;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;

/**
 * This class is a utility class that provides several functions for reading
 * data from java buffers.
 * 
 * @author squid
 * 
 */
public class BufferUtils {
	/**
	 * Read a Quaternion from a ByteBuffer. This actually reads the Quaternion as
	 * shorts, not floats, and then converts them to float values.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * 
	 * @return The quaternion.
	 */
	public static Quaternion getQuaternion (ByteBuffer bb) {
		Quaternion quat = new Quaternion ();

		short value = bb.getShort ();
		quat.x = ((value > 0 ? value - 32767f : value + 32767f) / 32767f);
		value = bb.getShort ();
		quat.y = ((value > 0 ? value - 32767f : value + 32767f) / 32767f);
		value = bb.getShort ();
		quat.z = ((value > 0 ? value - 32767f : value + 32767f) / 32767f);
		value = bb.getShort ();
		quat.w = ((value > 0 ? value - 32767f : value + 32767f) / 32767f);

		return quat;
	}

	/**
	 * This method is used to read an array of Quaternion objects from a
	 * ByteBuffer. The number of Quaternion objects read and the position in the
	 * buffer are denoted by the M2CountOffsetPair passed into the method. This
	 * method has the ability to reset the buffer's position once the data is read
	 * by setting the restorePosition flag to true.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of Quat4f objects.
	 */
	public static Quaternion[] getQuaternionArray (ByteBuffer bb, M2CountOffsetPair cop, boolean restorePosition) {
		int position = bb.position ();
		bb.position (cop.getOffset ());

		Quaternion[] data = new Quaternion[cop.getCount ()];
		for (int i = 0; i < data.length; i++) {
			data[i] = getQuaternion (bb);
		}

		if (restorePosition) {
			bb.position (position);
		}

		return data;
	}

	/**
	 * This method is used to read an array of Quaternion objects from a
	 * ByteBuffer. The number of Quaternion objects read and the position in the
	 * buffer are denoted by the M2CountOffsetPair passed into the method.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of Quat4f objects.
	 */
	public static Quaternion[] getQuaternionArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getQuaternionArray (bb, cop, false);
	}

	public static Vector3f getVector3f (ByteBuffer bb) {
		Vector3f vector = new Vector3f ();

		vector.setX (bb.getFloat ());
		vector.setY (bb.getFloat ());
		vector.setZ (bb.getFloat ());

		return vector;
	}

	/**
	 * Read a Vector3f from a ByteBuffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * 
	 * @return The vector.
	 */
	public static Vector2f getVector2f (ByteBuffer bb) {
		Vector2f vector = new Vector2f ();

		vector.setX (bb.getFloat ());
		vector.setY (bb.getFloat ());

		return vector;
	}

	/**
	 * This method is used to read an array of Vector3f objects from a ByteBuffer.
	 * The number of Vector3f objects read and the position in the buffer are
	 * denoted by the M2CountOffsetPair passed into the method. This method has
	 * the ability to reset the buffer's position once the data is read by setting
	 * the restorePosition flag to true.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of Vector3f objects.
	 */
	public static Vector3f[] getVector3fArray (ByteBuffer bb, M2CountOffsetPair cop, boolean restorePosition) {
		int position = bb.position ();
		bb.position (cop.getOffset ());

		Vector3f[] data = new Vector3f[cop.getCount ()];
		for (int i = 0; i < data.length; i++) {
			data[i] = getVector3f (bb);
		}

		if (restorePosition) {
			bb.position (position);
		}

		return data;
	}

	/**
	 * This method is used to read an array of Vector3f objects from a ByteBuffer.
	 * The number of Vector3f objects read and the position in the buffer are
	 * denoted by the M2CountOffsetPair passed into the method.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of Vector3f objects.
	 */
	public static Vector3f[] getVector3fArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getVector3fArray (bb, cop, false);
	}
	
	/**
	 * Read an array of floats from a ByteBuffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * @param size
	 *          The number of entries in the array.
	 * 
	 * @return The new array of floats.
	 */
	public static float[] getFloatArray (ByteBuffer bb, int size) {
		float[] data = new float[size];

		for (int i = 0; i < size; i++) {
			data[i] = bb.getFloat ();
		}

		return data;
	}

	/**
	 * This method is used to read an array of floats from a ByteBuffer. The
	 * number of floats read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method. This method has the ability to
	 * reset the buffer's position once the data is read by setting the
	 * restorePosition flag to true.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of floats.
	 */
	public static float[] getFloatArray (ByteBuffer bb, M2CountOffsetPair cop, boolean restorePosition) {
		//
		// Save the buffer's current position and reposition to the start of the
		// data.
		//
		int position = bb.position ();
		bb.position (cop.getOffset ());

		//
		// Read in the data. After reading, reset the buffer's position if
		// necessary.
		//
		float[] data = getFloatArray (bb, cop.getCount ());
		if (restorePosition) {
			bb.position (position);
		}

		return data;
	}

	/**
	 * This method is used to read an array of floats from a ByteBuffer. The
	 * number of floats read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of floats.
	 */
	public static float[] getFloatArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getFloatArray (bb, cop, false);
	}

	/**
	 * Read an array of M2CountOffsetPair objects from a byte buffer.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of count/offset pairs.
	 */
	public static M2CountOffsetPair[] getCountOffsetPairArray (ByteBuffer bb, M2CountOffsetPair cop,
			boolean restorePosition) {
		//
		// Save the buffer's current position and reposition to the start of the
		// data.
		//
		int position = bb.position ();
		bb.position (cop.getOffset ());

		//
		// Read in the data. After reading, reset the buffer's position if
		// necessary.
		//
		M2CountOffsetPair[] data = new M2CountOffsetPair[cop.getCount ()];
		for (int i = 0; i < data.length; i++) {
			data[i] = new M2CountOffsetPair (bb);
		}

		if (restorePosition) {
			bb.position (position);
		}

		return data;
	}

	/**
	 * Read an array of M2CountOffsetPair objects from a byte buffer.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of count/offset pairs.
	 */
	public static M2CountOffsetPair[] getCountOffsetPairArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getCountOffsetPairArray (bb, cop, false);
	}

	/**
	 * Read an array of shorts from a ByteBuffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * @param size
	 *          The number of entries in the array.
	 * 
	 * @return The new array of shorts.
	 */
	public static short[] getShortArray (ByteBuffer bb, int size) {
		short[] data = new short[size];

		for (int i = 0; i < size; i++) {
			data[i] = bb.getShort ();
		}

		return data;
	}

	/**
	 * This method is used to read an array of shorts from a ByteBuffer. The
	 * number of shorts read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method. This method has the ability to
	 * reset the buffer's position once the data is read by setting the
	 * restorePosition flag to true.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of shorts.
	 */
	public static short[] getShortArray (ByteBuffer bb, M2CountOffsetPair cop, boolean restoreBufferPosition) {
		int position = bb.position ();
		bb.position (cop.getOffset ());
		short[] data = getShortArray (bb, cop.getCount ());

		if (restoreBufferPosition) {
			bb.position (position);
		}
		return data;
	}

	/**
	 * This method is used to read an array of shorts from a ByteBuffer. The
	 * number of shorts read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of shorts.
	 */
	public static short[] getShortArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getShortArray (bb, cop, false);
	}

	/**
	 * Read an array of integers from a ByteBuffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * @param size
	 *          The number of entries in the array.
	 * 
	 * @return The new array of integers.
	 */
	public static int[] getIntArray (ByteBuffer bb, int size) {
		int[] data = new int[size];

		for (int i = 0; i < size; i++) {
			data[i] = bb.getInt ();
		}

		return data;
	}

	/**
	 * This method is used to read an array of ints from a ByteBuffer. The number
	 * of ints read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method. This method has the ability to
	 * reset the buffer's position once the data is read by setting the
	 * restorePosition flag to true.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restorePosition
	 *          True if we should restore the buffer's position after reading the
	 *          data.
	 * 
	 * @return An array of integers.
	 */
	public static int[] getIntArray (ByteBuffer bb, M2CountOffsetPair cop, boolean restorePosition) {
		//
		// Save the buffer's current position and reposition to the start of the
		// data.
		//
		int position = bb.position ();
		bb.position (cop.getOffset ());

		//
		// Read in the data. After reading, reset the buffer's position if
		// necessary.
		//
		int[] data = getIntArray (bb, cop.getCount ());
		if (restorePosition) {
			bb.position (position);
		}

		return data;
	}

	/**
	 * This method is used to read an array of ints from a ByteBuffer. The number
	 * of ints read and the position in the buffer are denoted by the
	 * M2CountOffsetPair passed into the method.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of integers.
	 */
	public static int[] getIntArray (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getIntArray (bb, cop, false);
	}

	/**
	 * Read an array of bytes from a ByteBuffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * @param size
	 *          The number of entries in the array.
	 * 
	 * @return The new array of bytes.
	 */
	public static byte[] getByteArray (ByteBuffer bb, int size) {
		byte[] data = new byte[size];

		for (int i = 0; i < size; i++) {
			data[i] = bb.get ();
		}

		return data;
	}

	/**
	 * Read a null terminated string from a byte buffer.
	 * 
	 * @param bb
	 *          The byte buffer to read from.
	 * 
	 * @return The string.
	 */
	public static String getString (ByteBuffer bb) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream ();

		byte data;
		while ((data = bb.get ()) != 0) {
			baos.write (data);
		}

		return baos.toString ();
	}

	/**
	 * This method is used to read a String from a ByteBuffer using a count/offset
	 * pair to position the buffer and determine the string size. This method can
	 * also, optionally, restore the buffer's position to its previous location
	 * after reading the string.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * @param restoreBufferPosition
	 *          True if the buffer position should be restored after reading the
	 *          string.
	 * 
	 * @return An array of integers.
	 */
	public static String getString (ByteBuffer bb, M2CountOffsetPair cop, boolean restoreBufferPosition) {
		int position = bb.position ();
		bb.position (cop.getOffset ());
		String result = BufferUtils.getString (bb);
		if (restoreBufferPosition) {
			bb.position (position);
		}
		return result;
	}

	/**
	 * This method is used to read a String from a ByteBuffer using a count/offset
	 * pair to position the buffer and determine the string size.
	 * 
	 * @param bb
	 *          The ByteBuffer to read the data from.
	 * @param cop
	 *          The count/offset pair to use for referencing the data
	 * 
	 * @return An array of integers.
	 */
	public static String getString (ByteBuffer bb, M2CountOffsetPair cop) {
		return BufferUtils.getString (bb, cop, false);
	}
}
